/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Implementation for Image interface client library.
 * \author Antti Maula <antti.maula@tkk.fi>
 */

#include "ImageClient.hpp"
#include "ImageData.hpp"

//*****************************************************************************
using namespace MaCI::Image;
//*****************************************************************************
//*****************************************************************************
#define GIMI_CLIENT_API (20000)
#include "gimi.h"
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

CImageClient::CImageClient(gimi::GIMI *aGIMIPtr, 
                           const int aInterfaceMinor)
  : CSync(2,1),
    CThread(1),
    CMaCI(aGIMIPtr, GIMI_PROTOCOL_MACI_IMAGE, aInterfaceMinor),
    iImageSequence(0),
    iLastImageData(),
    iSourceInfoMap(),
    iSourceInfoMapReceived(false),
    iImageClientCallback(NULL)
{
  dPrint(4,"CImageClient %p constructed", this);
}
//*****************************************************************************

CImageClient::~CImageClient()
{
  DoClose();
  dPrint(4,"CImageClient %p destructed", this);
}
//*****************************************************************************

MaCI::EMaCIError CImageClient::DoOpen(void)
{

  EMaCIError result = KMaCIError;
  
  // Checks.
  if( !IsThreadRunning() ) {

    // Do Subs. using the MaCI provided function.
    result = SubscribeToInterface();

    // Modify the "On the Fly" queue size
    iGIMIPtr->setQueueMaxSize(GIMI_PROTOCOL_MACI_IMAGE, iInterfaceMinor, 1);

    // If all inits were fine...
    if (result == KMaCIOK) {
      // Start the Thread
      SetActive(true);
      CThread::RunThread();
      dPrint(4,"CImageClient::DoOpen() was succesfull.");
    }

  } else {
    dPrint(4,"ImageClient already Active, not Opening again.");
    result = KMaCINotActive;

  }
  
  return result;
}
//*****************************************************************************

MaCI::EMaCIError CImageClient::DoClose(void)
{
  EMaCIError result = KMaCIError;
  
  if(IsThreadRunning() || IsActive()) {
    SetActive(false);
    
    // Unsubscribe from the interface.
    UnsubscribeToInterface(TimeoutValue(2000));

    // Now Notify() event waiters (Which will check the IsActive() and terminate).
    Notify(KEventSourceInfoMap);
    Notify(KEventNewImage);

    // Wait for the processing thread to complete (... And Only if it
    // is Running)
    if (IsThreadRunning()) CThread::WaitThread();
    
    
    dPrint(ODVINFO,"CImageClient instance %p closed.", this);

  } else {
    dPrint(4, "ImageClient not active, so not Closing.");
    result = KMaCINotActive;

  }
  
  return result;
}
//*****************************************************************************

bool CImageClient::GetImageData(CImageData &aImgData, 
                                unsigned int *aImgSeq,
                                const unsigned int aTimeout_ms)
{
  bool result = false;
  
  Lock();

  
  if (aImgSeq == NULL) {
    // No sequence number defined. Just do direct copy of local data - no sync
    aImgData = iLastImageData;
    result = true;
    
  } else {
    dPrint(8,"GetImageData(): iImageSequence = %u, *aImgSeq = %u", 
           iImageSequence, *aImgSeq);
    
    if (iImageSequence <= *aImgSeq) {
      int time_left = aTimeout_ms;
      unsigned int time_begin = ownTime_get_ms();
      
      // The available scan is older than requested - must wait for time_left
      do {
        Wait(time_left, KEventNewImage);
        time_left = aTimeout_ms - ownTime_get_ms_since(time_begin);
	
      } while(IsActive() && iImageSequence < *aImgSeq && time_left > 0);
      
      // Now, check whether the waiting resulted in the correct sequence
      if (iImageSequence > *aImgSeq) {
        // Yes :)
        aImgData = iLastImageData;
        *aImgSeq = iImageSequence;
        result = true;
	
      } else {
        // No :(
        dPrint(9, "Failed to get ImageData! (Timeout %dms)",
               aTimeout_ms);
      }
      
    } else {
      // The available scan is newer or matching age than requested.
      aImgData = iLastImageData;
      *aImgSeq = iImageSequence;
      result = true;
    }
  }
  
  Unlock();
  
  return result;
}
//*****************************************************************************

bool CImageClient::GetSourceInfoMap(TSourceInfoMap &aMap, 
                                    const unsigned int aTimeout_ms, 
                                    const bool aRefresh)
{
  bool result = false;
  const ownTime_ms_t begin = ownTime_get_ms();

  // Send request if mapsize == 0, or refresh flag is set.
  Lock(); // Lock Held --->

  if (!iSourceInfoMapReceived || aRefresh) {
    // Clear.
    iSourceInfoMap.clear();
    iSourceInfoMapReceived = false;

    Unlock(); // Lock Free! --->

    // Construct command to set speed
    CImageData ctrl;
    ctrl.CreateInternalBinBag();
    ctrl.SetCommand(TCommand(KCommandGetSourceInfoArray));
    
    // Send the command
    const EMaCIError e = SendInterfaceMessage(ctrl.GetBinBagContainerPtr()->GetBuffer(),
                                              ctrl.GetBinBagContainerPtr()->GetBufferSize());
    if (e != KMaCIOK) {
      dPrint(2,"Failed to Send interfacemessage! Unable to receive SourceInfoMap!");
      return false; // Return immediately, Lock() is not held, so can return here.
    }
    Lock(); // Lock Held --->
  }
  
  // Enter waiting loop only if the map is empty (if refreshed, was
  // cleared in previous if clause)
  if (!iSourceInfoMapReceived) {
    // Marking the time_left to full 
    ownTime_ms_delta_t time_left = ownTime_get_ms_left(aTimeout_ms, begin);  
    while(IsActive() && 
          time_left > 0 && 
          !iSourceInfoMapReceived) {
      Wait(time_left, KEventSourceInfoMap);
      time_left = ownTime_get_ms_left(aTimeout_ms, begin);
    }
  }
  
  // If previous waiting operation was succesfull
  if (iSourceInfoMapReceived) {
    aMap = iSourceInfoMap;
    result = true;

  } else {
    dPrint(4,"Could not Get SourceInfoMap. (Timeout %ums)", aTimeout_ms);
    
  }
  Unlock(); // Lock free! --->

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

bool CImageClient::GetSourceInfo(TSourceInfo &aInfo, 
                                 const unsigned int aIndex, 
                                 const unsigned int aTimeout_ms, 
                                 const bool aRefresh)
{
  bool result = false;
  TSourceInfoMap sim;  

  result = GetSourceInfoMap(sim, aTimeout_ms, aRefresh);

  // If succesfully fetched map.
  if (result) {
    const ITERATOR(sim) imap = sim.find(aIndex);
    if (imap != sim.end()) {
      aInfo = imap->second;
      result = true;
    }
  }
  
  return result;
}
//*****************************************************************************
//*****************************************************************************

int CImageClient::ProcessMessage(const gimi::GIMIMessage &msg)
{
  int result = 0;
  gim::binbag::CBinBag bb;
  CImageData id;
  bool r;

  r = bb.Decode(msg.getData(), msg.getDatalength());
  if (r) {
    // Succesfully decoded as BinBag.
    r = id.DecodeFrom(&bb);
    if (r) {
      // Succesfully decoded as Image, now handle contents
      Lock();
      if (id.IsImageContainer()) {
        iLastImageData = id;
        ++iImageSequence;
        Notify(KEventNewImage);
        dPrint(10,"Got ImageData, Sequence now %u", iImageSequence);
      }
      
      // Check whether the element contains
      if (id.IsSourceInfoContainer()) {
        iSourceInfoMap = id.GetSourceInfoMap();
        iSourceInfoMapReceived = true;
        Notify(KEventSourceInfoMap);
      }
      Unlock();

      // Call the handler if set.
      if (iImageClientCallback) {
        dPrint(8,"Got Callback for ImageDataEvent - executing!");
        iImageClientCallback->OnImageDataEvent(id,
                                               iImageSequence,
                                               *this);
        dPrint(8,"Callback for ImageDataEvent - execution complete!");
      } 


    } else {
      dPrint(4,"WARNING: Received Image interface message, but failed to decode as ImageData!");
      
    }
  }
  
  return result;
}
//*****************************************************************************

int CImageClient::ThreadFunction(const int aThreadNumber)
{
  int result = 0;
  // const unsigned int hard_error_count_limit = 10;
  //unsigned int hard_error_count = 0;
  EMaCIError e;
  gimi::GIMIMessage gmsg;
  
  assert(aThreadNumber == 0);
  assert(iGIMIPtr != NULL);
  
  dPrint(8, "GIMIMessage handler started");
  
  // Run as long as the thread should be active
  while(IsActive()) {
    // Call the ReceiveMessage wrapper with 1s timeout
    e = ReceiveInterfaceMessage(gmsg, 1000*60*60*24);
    if (e == KMaCIOK) {
      ProcessMessage(gmsg);

      // Reset error counter.
      // if (hard_error_count) hard_error_count = 0;

    } else if (e != KMaCITimeout) {
      /*  // Increment hard error count
      ++hard_error_count;
      
      // Check whether the hard error count limit is still under limit. 
      if (hard_error_count < hard_error_count_limit) {
        // Other error, try to recover by sleeping one second.
        dPrint(10,"ProcessMessage returned error %u, trying to recover by sleeping 1000ms (%u attempts left)",
               e, hard_error_count_limit-hard_error_count);

      } else {
        dPrint(ODERROR,"Error Recovery failed, ImageClient ThreadFunction terminating...");
        // Break the while().
        break;
      }
      */
     

      if(IsActive()){
        dPrint(3,"ProcessMessage returned error %u and client still active. Trying to recover by sleeping 1000ms ",
             e);
        ownSleep_ms(1000);
      }else{
        dPrint(3,"ProcessMessage returned error %u.",
               e);
      }
    }
  }
  
  dPrint(8, "GIMIMessage handler stopped");
  SetActive(false);

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
